/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.peer.dao.statistics;

import java.util.TimeZone;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.ourgrid.common.config.Configuration;
import org.ourgrid.common.statistics.beans.peer.Peer;
import org.ourgrid.common.statistics.beans.peer.monitor.LocalPeerStatusChange;
import org.ourgrid.common.statistics.beans.status.PeerStatus;
import org.ourgrid.common.statistics.util.hibernate.HibernateUtil;
import org.ourgrid.peer.PeerConfiguration;

import br.edu.ufcg.lsd.commune.Application;
import br.edu.ufcg.lsd.commune.container.ContainerContext;
import br.edu.ufcg.lsd.commune.container.logging.CommuneLogger;
import br.edu.ufcg.lsd.commune.container.logging.CommuneLoggerFactory;
import br.edu.ufcg.lsd.commune.container.servicemanager.ServiceManager;
import br.edu.ufcg.lsd.commune.network.certification.CertificationUtils;

/**
 * 
 */
public class PeerDAO extends EntityDAO {
	private CommuneLogger logger;
	
	public PeerDAO(Application application) {
		super(application);
		 logger = CommuneLoggerFactory.getInstance().gimmeALogger(getClass());
	}

	public Peer findByID(String address) {
		logger.enter();
		
		Criteria criteria = HibernateUtil.getSession().createCriteria(Peer.class);
		criteria.add(Restrictions.eq("address", address));
		Peer peer = (Peer) criteria.uniqueResult();
			
		logger.leave();
		return peer;
	}

	public Peer findUpPeerByID(String address) {
		logger.enter();
		
		Criteria criteria = HibernateUtil.getSession().createCriteria(Peer.class);
		criteria.add(Restrictions.eq("address", address));
		criteria.add(Restrictions.eq("currentStatus", PeerStatus.UP));
		Peer peer = (Peer) criteria.uniqueResult();
			
		logger.leave();
		return peer;
	}
	
	public Peer getPeerBySubjectDN(String peerDNData) {
		Criteria criteria = HibernateUtil.getSession().createCriteria(Peer.class);
		criteria.add(Restrictions.eq("DNdata", peerDNData));
		return (Peer) criteria.uniqueResult();
	}

	public void insert(Peer peer) {
		logger.enter();
		Session session = HibernateUtil.getSession();
		session.save(peer);
		session.flush();
		logger.leave();
	}

	public void insertStatusChange(LocalPeerStatusChange statusChange) {
		logger.enter();
		Session session = HibernateUtil.getSession();
		session.save(statusChange);
		session.flush();
		logger.leave();
	}
	
	public void update(Peer peer) {
		logger.enter();
		Session session = HibernateUtil.getSession();
		session.update(peer);
		session.flush();
		logger.leave();
	}
	
	public void updatePeerUptime(ContainerContext context) {
		
		String address = getMyAddress(context);
		Peer peer = findByID(address);
		
		//Peer is not up, that's ok for now.
		if (peer == null) {
			return;
		}
		
		peer.setLastUpTime(now());
		
		update(peer);
	}
	
	public Long getFirstDBUpdateDate() {
		
		Criteria criteria = HibernateUtil.getSession().createCriteria(Peer.class);
		criteria.setProjection(Projections.min("lastModified"));
		Long minPeer = (Long) criteria.uniqueResult();
		
		Criteria criteriaSC = HibernateUtil.getSession().createCriteria(LocalPeerStatusChange.class);
		criteriaSC.setProjection(Projections.min("lastModified"));
		Long minStatusChange = (Long) criteriaSC.uniqueResult();
		
		return Math.min(minPeer, minStatusChange);
	}
			
	
	public Long getPeerLastUptime(ContainerContext context) {
		
		String address = getMyAddress(context);
		Peer peer = findByID(address);
		
		//Peer is not up, that's ok for now.
		if (peer == null) {
			return null;
		}
		
		return peer.getLastUpTime();
		
	}
	
	public Peer updatePeer(ServiceManager serviceManager) {
		ContainerContext context = serviceManager.getContainerContext();
		
		String address = getMyAddress(context);
		PeerDAO peerDao = serviceManager.getDAO(PeerDAO.class);
		Peer peer = peerDao.getPeerBySubjectDN(CertificationUtils.getCertSubjectDN(serviceManager.getMyCertPath()));
		
		if (peer == null) {
			peer = new Peer();
			peer.setAddress(address);
			peer.setDNdata(CertificationUtils.getCertSubjectDN(
					serviceManager.getMyCertPath()));
			updatePeerObject(peer, serviceManager);
			
			insert(peer);
		} 
		else { 
			
			String description = context.getProperty(PeerConfiguration.PROP_DESCRIPTION);
			String email = context.getProperty(PeerConfiguration.PROP_EMAIL);
			String label = context.getProperty(PeerConfiguration.PROP_LABEL);
			String latitude = context.getProperty(PeerConfiguration.PROP_LATITUDE);
			String longitude = context.getProperty(PeerConfiguration.PROP_LONGITUDE);
			String dnData = CertificationUtils.getCertSubjectDN(serviceManager.getMyCertPath());
			String version = Configuration.VERSION.toString();
			
			//Test if at least one attribute was changed and reset the lastModified field.
			if (peer.getDescription() == null || !peer.getDescription().equals(description)
					|| peer.getEmail() == null || !peer.getEmail().equals(email)
					|| peer.getLabel() == null || !peer.getLabel().equals(label)
					|| peer.getLatitude() == null || !peer.getLatitude().equals(latitude)
					|| peer.getLongitude() == null || !peer.getLongitude().equals(longitude)
					|| peer.getVersion() == null || !peer.getVersion().equals(version)
					|| peer.getDNdata() == null || !peer.getDNdata().equals(dnData)
					|| peer.getAddress() == null || !peer.getAddress().equals(address)) {
				
				updatePeerObject(peer, serviceManager);
			}	
			
			update(peer);
		}
		
		return peer;
	}
	
	private void updatePeerObject(Peer peer, ServiceManager serviceManager) {
		peer.setDescription(serviceManager.getContainerContext().getProperty(PeerConfiguration.PROP_DESCRIPTION));
		peer.setEmail(serviceManager.getContainerContext().getProperty(PeerConfiguration.PROP_EMAIL));
		peer.setLabel(serviceManager.getContainerContext().getProperty(PeerConfiguration.PROP_LABEL));
		peer.setLatitude(serviceManager.getContainerContext().getProperty(PeerConfiguration.PROP_LATITUDE));
		peer.setLongitude(serviceManager.getContainerContext().getProperty(PeerConfiguration.PROP_LONGITUDE));
		peer.setDNdata(CertificationUtils.getCertSubjectDN(serviceManager.getMyCertPath()));
		peer.setVersion(Configuration.VERSION.toString());
		peer.setTimezone(TimeZone.getDefault().getID());
		peer.setAddress(getMyAddress(serviceManager.getContainerContext()));
		
		peer.setLastModified(now());
	}

	public Peer getPeer(ContainerContext containerContext) {
		String address = getMyAddress(containerContext);
		return findByID(address);
	}

	public void insertStatusChange(Peer peer, PeerStatus status, Long timeOfChange) {
		
		LocalPeerStatusChange toUpChange = new LocalPeerStatusChange();
		toUpChange.setCurrentStatus(status);
		toUpChange.setLastModified(now());
		toUpChange.setPeer(peer);
		toUpChange.setVersion(Configuration.VERSION.toString());
		toUpChange.setTimeOfChange(timeOfChange);
		
		insertStatusChange(toUpChange);
		
	}

	public void remove(Peer peer) {
		
		logger.enter();
		
		Session session = HibernateUtil.getSession();
		session.delete(peer);
		session.flush();
		
		logger.leave();
		
	}


}
